#ifndef BITFLAGS_H
#define BITFLAGS_H

#include <type_traits>
#include <utility>
#include <cstdint>

namespace simodo::variable {

    /**
     * @brief Функция для создания битовой маски путем сдвига бита влево.
     *
     * @param n Позиция бита, который нужно установить.
     * @return constexpr Битовая маска, установленная на указанной позиции.
     */
    constexpr uint64_t bitShift(const uint64_t n) { return 1ULL << n; }


    /**
     * @brief Шаблон класса для управления битовыми флагами заданного перечислимого типа.
     *
     * @tparam Enum Тип перечисления, флаги которого будут управляться.
     */
    template<typename Enum>
    class BitFlags {
        static_assert(std::is_enum_v<Enum>, "BitFlags требует тип перечисления.");

        /// @brief Базовый тип перечисления, что обычно является численным типом.
        using UnderlyingType = std::underlying_type_t<Enum>;

        /// @brief Пустое "сырое" значение, что, очевидно, обычно равно просто нулю.
        static constexpr UnderlyingType EMPTY_RAW_VALUE = 0;

    public:

        /// @brief Пустой объект BitFlags с пустыми флагами.
        static constexpr BitFlags Empty = BitFlags();

        /// @brief Создает новый объект BitFlags с всеми флагами сброшенными.
        constexpr BitFlags() : _flags(EMPTY_RAW_VALUE) { }

        /**
         * @brief Конструктор копирования.
         *
         * @param other Другой объект BitFlags, который нужно скопировать.
         */
        BitFlags(const BitFlags & other) : _flags(other._flags) { }

        /**
         * @brief Конструктор из объекта BitFlags.
         *
         * @param other Другой объект BitFlags, из которого нужно сконструировать.
         */
        BitFlags(const BitFlags && other) noexcept : _flags(std::move(other._flags)) { }

        /**
         * @brief Конструктор из сырого значения.
         *
         * @param rawValue Сырое значение, представляющее флаги.
         */
        BitFlags(UnderlyingType rawValue) : _flags(rawValue) { }  // NOLINT(*-explicit-constructor)

        /**
         * @brief Конструктор из одного флага.
         *
         * @param flag Один флаг, который нужно установить.
         */
        BitFlags(Enum flag) : _flags(toRawValue(flag)) { } // NOLINT(*-explicit-constructor)

        /**
         * @brief Устанавливает определенный флаг.
         *
         * @param flag Флаг, который нужно установить.
         */
        void set(Enum flag) {
            _flags |= toRawValue(flag);
        }

        /**
         * @brief Сбрасывает определенный флаг.
         *
         * @param flag Флаг, который нужно сбросить.
         */
        void clear(Enum flag) {
            _flags &= ~toRawValue(flag);
        }

        /**
         * @brief Проверяет, установлен ли определенный флаг.
         *
         * @param flag Флаг для проверки.
         * @return true, если флаг установлен, в противном случае false.
         */
        bool isSet(Enum flag) const {
            return (_flags & toRawValue(flag)) != EMPTY_RAW_VALUE;
        }

        /**
         * @brief Переключает определенный флаг.
         *
         * @param flag Флаг для переключения.
         */
        void toggle(Enum flag) {
            _flags ^= toRawValue(flag);
        }

        /**
         * @brief Сбрасывает все флаги в ноль.
         */
        void reset() {
            _flags = EMPTY_RAW_VALUE;
        }

        /**
         * @brief Добавляет флаги, используя битовую маску.
         *
         * @param mask Битовая маска, указывающая, какие флаги добавить.
         */
        void add(UnderlyingType mask) {
            _flags |= mask;
        }

        /**
         * @brief Добавляет флаги из другого объекта BitFlags.
         *
         * @param otherFlags Объект BitFlags, флаги которого нужно добавить.
         */
        void add(const BitFlags & otherFlags) {
            _flags |= otherFlags.getRawValue();
        }

        /**
         * @brief Возвращает сырое значение флагов.
         *
         * @return Сырое значение, представляющее флаги.
         */
        UnderlyingType getRawValue() const {
            return _flags;
        }

        /**
         * @brief Перегрузка оператора | для добавления одного флага к объекту BitFlags.
         *
         * @param flag Флаг, который нужно добавить.
         * @return Новый объект BitFlags с добавленным флагом.
         */
        BitFlags operator|(Enum flag) const {
            BitFlags result(*this);
            result.set(flag);
            return result;
        }

        /**
         * @brief Перегрузка оператора присваивания для объекта BitFlags.
         *
         * @param other Другой объект BitFlags, значения которого нужно присвоить.
         * @return Ссылка на объект BitFlags, который был присвоен.
         */
        BitFlags& operator=(const BitFlags& other) {
            if (this != &other) {
                _flags = other._flags;
            }
            return *this;
        }

        /**
         * @brief Перегрузка оператора равенства для сравнения двух объектов BitFlags.
         *
         * @param other Другой объект BitFlags для сравнения.
         * @return true, если флаги двух объектов равны, в противном случае false.
         */
        bool operator==(const BitFlags& other) const {
            return _flags == other._flags;
        }

        /**
         * @brief Перегрузка оператора неравенства для сравнения двух объектов BitFlags.
         *
         * @param other Другой объект BitFlags для сравнения.
         * @return true, если флаги двух объектов не равны, в противном случае false.
         */
        bool operator!=(const BitFlags& other) const {
            return !(*this == other);
        }

    private:
        UnderlyingType _flags; /**< Основное хранилище для флагов. */

        static UnderlyingType toRawValue(Enum flag) { return static_cast<UnderlyingType>(flag); }
    };

}

#endif //BITFLAGS_H
